package com.example.config;

import cn.dev33.satoken.dao.SaSessionForJacksonCustomized;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.model.LoginInfoModel;
import com.example.service.LoginService;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 业务聚合
 *
 * @version [V2.0]
 * @package com.example.sso.server.config;
 * @description:[用一句话描述该文件做什么]
 * @author: [zhuben]
 * @date: [2024-01-24 16:31]
 * @copyright: 2024 echovin.com
 */
@Component
@Primary
public class SaTokenDaoDatabase implements SaTokenDao {
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
    public ObjectMapper objectMapper;
    public StringRedisTemplate stringRedisTemplate;
    public RedisTemplate<String, Object> objectRedisTemplate;
    public boolean isInit;

    @Resource
    private LoginService loginService;
    private String type = "redis";

    public SaTokenDaoDatabase() {
    }
    public SaTokenDaoDatabase(LoginService loginService, String type) {
        this.loginService = loginService;
        this.type = type;
    }

    @Autowired
    public void init(RedisConnectionFactory connectionFactory) {
        if (!this.isInit && "redis".equals(type)) {
            StringRedisSerializer keySerializer = new StringRedisSerializer();
            GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();

            try {
                Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper");
                field.setAccessible(true);
                this.objectMapper = (ObjectMapper)field.get(valueSerializer);
                this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                JavaTimeModule timeModule = new JavaTimeModule();
                timeModule.addSerializer(new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
                timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
                timeModule.addSerializer(new LocalDateSerializer(DATE_FORMATTER));
                timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER));
                timeModule.addSerializer(new LocalTimeSerializer(TIME_FORMATTER));
                timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER));
                this.objectMapper.registerModule(timeModule);
                SaStrategy.instance.createSession = (sessionId) -> {
                    return new SaSessionForJacksonCustomized(sessionId);
                };
            } catch (Exception var6) {
                System.err.println(var6.getMessage());
            }

            StringRedisTemplate stringTemplate = new StringRedisTemplate();
            stringTemplate.setConnectionFactory(connectionFactory);
            stringTemplate.afterPropertiesSet();
            RedisTemplate<String, Object> template = new RedisTemplate();
            template.setConnectionFactory(connectionFactory);
            template.setKeySerializer(keySerializer);
            template.setHashKeySerializer(keySerializer);
            template.setValueSerializer(valueSerializer);
            template.setHashValueSerializer(valueSerializer);
            template.afterPropertiesSet();
            this.stringRedisTemplate = stringTemplate;
            this.objectRedisTemplate = template;
            this.isInit = true;
        }
    }

    @Override
    public String get(String key) {
        if ("redis".equals(type)){
            return (String)this.stringRedisTemplate.opsForValue().get(key);
        }
        return loginService.getByKey(key);
    }

    @Override
    public void set(String key, String value, long timeout) {
        if ("redis".equals(type)){
            if (timeout != 0L && timeout > -2L) {
                if (timeout == -1L) {
                    this.stringRedisTemplate.opsForValue().set(key, value);
                } else {
                    this.stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
                }

            }
        }

        if (timeout != 0L && timeout > -2L) {
            LoginInfoModel loginInfoModel = loginService.getModelByKey(key);

            if (loginInfoModel == null){
                loginInfoModel = new LoginInfoModel();
                loginInfoModel.setId(UUID.randomUUID().toString());

            }
            loginInfoModel.setKeystr(key);
            loginInfoModel.setResult(value);
            if (timeout == -1L) {
                loginService.saveOrUpdate(loginInfoModel);
            } else {
                loginInfoModel.setExpireTime(new Date(System.currentTimeMillis()+(timeout*1000L)));
                loginService.saveOrUpdate(loginInfoModel);
            }

        }
    }

    @Override
    public void update(String key, String value) {
        if ("redis".equals(type)){
            long expire = this.getTimeout(key);
            if (expire != -2L) {
                this.set(key, value, expire);
            }
        }
        long expire = this.getTimeout(key);
        if (expire != -2L) {
            this.set(key, value, expire);
        }
    }

    @Override
    public void delete(String key) {
        if ("redis".equals(type)){
            this.stringRedisTemplate.delete(key);
        }
        loginService.removeByKey(key);
    }

    @Override
    public long getTimeout(String key) {
        if ("redis".equals(type)){
            return this.stringRedisTemplate.getExpire(key);
        }
        String value = this.get(key);
        if (value == null || "".equals(value)){
            LoginInfoModel loginInfoModel = loginService.getModelByKey(key);
            if (loginInfoModel == null || loginInfoModel.getExpireTime() == null){
                return 0;
            }
            Date expireTime = loginInfoModel.getExpireTime();
            return (expireTime.getTime() - System.currentTimeMillis()) < 0 ? 0 : (expireTime.getTime() - System.currentTimeMillis())/1000L;
        }else{
            return 0;
        }
    }

    @Override
    public void updateTimeout(String key, long timeout) {
        if ("redis".equals(type)){
            if (timeout == -1L) {
                long expire = this.getTimeout(key);
                if (expire != -1L) {
                    this.set(key, this.get(key), timeout);
                }

            } else {
                this.stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
            }
        }
        if (timeout == -1L) {
            long expire = this.getTimeout(key);
            if (expire != -1L) {
                this.set(key, this.get(key), timeout);
            }
        } else {
            this.set(key, this.get(key), timeout);
        }
    }

    @Override
    public Object getObject(String key) {
        if ("redis".equals(type)){
            return this.objectRedisTemplate.opsForValue().get(key);
        }
        LoginInfoModel loginInfoModel = loginService.getModelByKey(key);
        if (loginInfoModel == null || "".equals(loginInfoModel.getResult()) || loginInfoModel.getResult() == null){
            return null;
        }
        return JSONObject.parseObject(loginInfoModel.getResult(), SaSession.class);
    }

    @Override
    public void setObject(String key, Object object, long timeout) {
        if ("redis".equals(type)){
            if (timeout != 0L && timeout > -2L) {
                if (timeout == -1L) {
                    this.objectRedisTemplate.opsForValue().set(key, object);
                } else {
                    this.objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS);
                }

            }
        }
        if (timeout != 0L && timeout > -2L) {
            LoginInfoModel loginInfoModel = loginService.getModelByKey(key);
            if (loginInfoModel == null){
                loginInfoModel = new LoginInfoModel();
                loginInfoModel.setId(UUID.randomUUID().toString());
            }
            if (timeout == -1L) {
                //this.objectRedisTemplate.opsForValue().set(key, object);
                loginInfoModel.setKeystr(key);
                loginInfoModel.setResult(JSONObject.toJSONString(object));
                loginService.saveOrUpdate(loginInfoModel);
            } else {
                //this.objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS);
                loginInfoModel.setKeystr(key);
                loginInfoModel.setResult(JSONObject.toJSONString(object));
                long timestamp = System.currentTimeMillis()+(timeout*1000L);
                loginInfoModel.setExpireTime(new Date(timestamp));
                loginService.saveOrUpdate(loginInfoModel);
            }

        }
    }

    @Override
    public void updateObject(String key, Object object) {
        long expire = this.getObjectTimeout(key);
        if (expire != -2L) {
            this.setObject(key, object, expire);
        }
    }

    @Override
    public void deleteObject(String key) {
        if ("redis".equals(type)){
            this.objectRedisTemplate.delete(key);
        }
        loginService.deleteByKey(key);
    }

    @Override
    public long getObjectTimeout(String key) {
        if ("redis".equals(type)){
            return this.objectRedisTemplate.getExpire(key);
        }
        LoginInfoModel loginInfoModel = loginService.getModelByKey(key);
        if (loginInfoModel == null || loginInfoModel.getExpireTime() == null){
            return 0;
        }
        Date expireTime = loginInfoModel.getExpireTime();
        return (expireTime.getTime() - System.currentTimeMillis()) < -1 ? 0 : (expireTime.getTime() - System.currentTimeMillis());
    }

    @Override
    public void updateObjectTimeout(String key, long timeout) {
        if (timeout == -1L) {
            long expire = this.getObjectTimeout(key);
            if (expire != -1L) {
                this.setObject(key, this.getObject(key), timeout);
            }

        } else {
            if ("redis".equals(type)){
                this.objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
            }
            this.setObject(key, this.getObject(key), timeout);
        }
    }

    @Override
    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
        if ("redis".equals(type)){
            Set<String> keys = this.stringRedisTemplate.keys(prefix + "*" + keyword + "*");
            List<String> list = new ArrayList(keys);
            return SaFoxUtil.searchList(list, start, size, sortType);
        }
        Set<String> keys = loginService.selectLikeByKey(keyword);
        List<String> list = new ArrayList(keys);
        return SaFoxUtil.searchList(list, start, size, sortType);
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}
